using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace Builder
{
  [Serializable]
  public class XPInfo
  {
    public int GameMaxXP;
    public int DailyXP;

    public XPInfo()
    {
    }

    public XPInfo(int GmMax, int Daily)
    {
      GameMaxXP = GmMax;
      DailyXP = Daily;
    }
  }

  public static class TrainingLevelStrings
  {
    public const string INTENSE = "Intense";
    public const string NORMAL = "Normal";
  }

  public static class AttributeStrings
  {
    public const string STR = "strength";
    public const string BLK = "blocking";
    public const string SPD = "speed";
    public const string TCK = "tackling";
    public const string AGI = "agility";
    public const string THR = "throwing";
    public const string STA = "stamina";
    public const string CAT = "catching";
    public const string JUM = "jumping";
    public const string CAR = "carrying";
    public const string VIS = "vision";
    public const string KCK = "kicking";
    public const string CON = "confidence";
    public const string PUN = "punting";
  }

  [Serializable]
  public class SoftcapRange
  {
    public int NumberOfSPs;
    public double Start;
    public double End;
    public SoftcapRange(int NumSPs, double StartRange, double EndRange)
    {
      Start = StartRange;
      End = EndRange;
      NumberOfSPs = NumSPs;
    }

    /// <summary>
    /// returns whether or not the passed value is in this Softcap's range
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public Boolean InRange(double value)
    {
      return value >= Start && value <= End;
    }

    /// <summary>
    /// returns the total number of SPs it would take to get the attribute from the start
    /// of this range to the passed value.
    /// </summary>
    /// <param name="Value"></param>
    /// <returns></returns>
    public double TotalSPsInThisRange(double Value)
    {
      if (Value > Start && Value <= End)
      {
        return (Value - Start) * NumberOfSPs;
      }
      else if (Value > End)
      {
        return ((End+.01) - Start) * NumberOfSPs;
      }

      return 0;
    }
  }

  [Serializable]
  public class PlayerBuild
  {
    public Dictionary<int, SoftcapRange> Softcaps = new Dictionary<int, SoftcapRange>();
    public int Level;
    public int XP;
    public int VetXP;
    public int Day;
    public int Season;
    public int UnspentSP;
    public int UnspentTP;
    public int UnspentBT;
    public int UnspentBoosts;
    public int UnspentVetSP;
    public string GameDays;
    public string PlayerName;


    /// <summary>
    /// Maximum % of game XP assumed for calculation of game XP.
    /// </summary>
    public int MaxGameXPPct
    {
      get { return m_MaxGameXPPct; }
      set
      {
        if (value >= 0 && value <= 100)
        {
          m_MaxGameXPPct = value;
        }
      }
    }

    /// <summary>
    /// PlayerDays mimics the Daily Look grid.
    /// </summary>
    public Dictionary<string, PlayerDay> PlayerDays = new Dictionary<string, PlayerDay>();

    private Dictionary<int, XPInfo> m_XPInfo = new Dictionary<int, XPInfo>();
    private Dictionary<int, int> m_TrainingPercents = new Dictionary<int, int>();
    private int m_MaxGameXPPct;
    private int m_PreSeasonTP;
    private int m_OffSeasonTP;
    private Boolean m_IncludeTodaysGame;

    #region Constructor
    public PlayerBuild(int CurrentSeason, int CurrentLevel, int CurrentXP, int CurrentSeasonDay, int MaxGameXPPct,
                       int CurrentUnspentSP, int CurrentUnspentTP, int CurrentUnspentBT,
                       int CurrentUnspentBoosts, int PreSeasonTP, int OffSeasonTP, Boolean IncludeTodaysGame, string _GameDays,
                       int VXP, int VSP, string Name)
    {
      Season = CurrentSeason;
      Level = CurrentLevel;
      XP = CurrentXP;
      Day = CurrentSeasonDay;
      UnspentTP = CurrentUnspentTP;
      UnspentSP = CurrentUnspentSP;
      UnspentBT = CurrentUnspentBT;
      UnspentBoosts = CurrentUnspentBoosts;
      m_MaxGameXPPct = MaxGameXPPct;
      m_PreSeasonTP = PreSeasonTP;
      m_OffSeasonTP = OffSeasonTP;
      m_IncludeTodaysGame = IncludeTodaysGame;
      GameDays = _GameDays;
      VetXP = VXP;
      UnspentVetSP = VSP;
      PlayerName = Name;

      //attempt to load from the training xml
      #region Get Train Amounts
      System.Xml.XmlDocument Data = new System.Xml.XmlDocument();
      //Make sure to load trnamts from the application's path
      String AppPath = System.IO.Path.GetDirectoryName(Application.ExecutablePath);
      Data.Load(System.IO.Path.Combine(AppPath, "trnamts.xml"));
      foreach (System.Xml.XmlNode mainnode in Data)
      {
        foreach (System.Xml.XmlNode lvlNode in mainnode.ChildNodes)
        {
          if (lvlNode.Name.ToLower() == "level")
          {
            int attr = 0;
            int pct = 0;
            foreach (System.Xml.XmlNode dataNode in lvlNode.ChildNodes)
            {
              if (dataNode.Name.ToLower() == "attr")
              {
                try
                {
                  attr = int.Parse(dataNode.InnerText);
                }
                catch
                {
                  attr = -1;
                }
              }
              else if (dataNode.Name.ToLower() == "pct")
              {
                try
                {
                  pct = int.Parse(dataNode.InnerText);
                }
                catch
                {
                  pct = 5;
                }
              }
            }
            if (attr > 0)
            {
              if (!m_TrainingPercents.ContainsKey(attr))
              {
                m_TrainingPercents.Add(attr, pct);
              }
              else
              {
                MessageBox.Show(String.Format("Conflicting training amounts for attribute level {0}.  Please review trnamts.xml file for duplicate <ATTR> entries for this level.", attr));
              }
            }
          }
        }
      }
      #endregion

      #region set softcaps
      Softcaps.Add(1, new SoftcapRange(1, 0, 48.06));
      Softcaps.Add(2, new SoftcapRange(2, 48.07, 60.51));
      Softcaps.Add(3, new SoftcapRange(3, 60.52, 67.97));
      Softcaps.Add(4, new SoftcapRange(4, 67.98, 73.24));
      Softcaps.Add(5, new SoftcapRange(5, 73.25, 77.28));
      Softcaps.Add(6, new SoftcapRange(6, 77.29, 80.53));
      Softcaps.Add(7, new SoftcapRange(7, 80.54, 83.25));
      Softcaps.Add(8, new SoftcapRange(8, 83.26, 85.58));
      Softcaps.Add(9, new SoftcapRange(9, 85.59, 87.60));
      Softcaps.Add(10, new SoftcapRange(10, 87.61, 89.40));
      Softcaps.Add(11, new SoftcapRange(11, 89.41, 91.01));
      Softcaps.Add(12, new SoftcapRange(12, 91.02, 92.46));
      Softcaps.Add(13, new SoftcapRange(13, 92.47, 93.79));
      Softcaps.Add(14, new SoftcapRange(14, 93.8, 95.00));
      Softcaps.Add(15, new SoftcapRange(15, 95.01, 96.13));
      Softcaps.Add(16, new SoftcapRange(16, 96.14, 97.18));
      Softcaps.Add(17, new SoftcapRange(17, 97.19, 98.15));
      Softcaps.Add(18, new SoftcapRange(18, 98.16, 99.06));
      Softcaps.Add(19, new SoftcapRange(19, 99.07, 99.92));
      Softcaps.Add(20, new SoftcapRange(20, 99.93, 100.73));
      Softcaps.Add(21, new SoftcapRange(21, 100.74, 101.5));
      Softcaps.Add(22, new SoftcapRange(22, 101.51, 102.23));
      Softcaps.Add(23, new SoftcapRange(23,102.24,102.92));
      Softcaps.Add(24, new SoftcapRange(24,102.93,103.58));
      Softcaps.Add(25, new SoftcapRange(25,103.59,104.21));
      Softcaps.Add(26, new SoftcapRange(26,104.22,104.81));
      Softcaps.Add(27, new SoftcapRange(27,104.82,105.39));
      Softcaps.Add(28, new SoftcapRange(28,105.4,105.94));
      Softcaps.Add(29, new SoftcapRange(29,105.95,106.47));
      Softcaps.Add(30, new SoftcapRange(30,106.48,200));
      #endregion

      LoadXPInfo();
    }
    #endregion


    /// <summary>
    /// Loads the XP Info data from the xp.xml file.
    /// </summary>
    private void LoadXPInfo()
    {
      //Make sure to load trnamts from the application's path
      String AppPath = System.IO.Path.GetDirectoryName(Application.ExecutablePath);
      if (System.IO.File.Exists(System.IO.Path.Combine(AppPath, "xp.xml")))
      {
        try
        {
          System.Xml.XmlDocument XPData = new System.Xml.XmlDocument();
          XPData.Load(System.IO.Path.Combine(AppPath, "xp.xml"));
          foreach (System.Xml.XmlNode mainnode in XPData)
          {
            foreach (System.Xml.XmlNode lvlNode in mainnode.ChildNodes)
            {
              if (lvlNode.Name == "XP")
              {
                int Level = 0;
                int GameXP = 0;
                int DailyXP = 0;
                foreach (System.Xml.XmlNode dataNode in lvlNode.ChildNodes)
                {
                  try
                  {
                    //make sure these are only integers that are being imported
                    if (dataNode.Name == "LVL")
                      Level = int.Parse(dataNode.InnerText.ToString());
                    else if (dataNode.Name == "GXP")
                      GameXP = int.Parse(dataNode.InnerText.ToString());
                    else
                      DailyXP = int.Parse(dataNode.InnerText.ToString());
                  }
                  catch (Exception ex)
                  {
                    MessageBox.Show(string.Format("Error importing XP information line.  Bad value '{0}'.  Error message {1}", dataNode.InnerText.ToString(), ex.Message));
                  }
                }
                if (Level > 0 && GameXP > 0 && DailyXP > 0)
                {
                  m_XPInfo.Add(Level, new XPInfo(GameXP, DailyXP));
                }
              }
            }
          }
        }
        catch (Exception ex)
        {
          Utility.ShowErrorMesssage(ex, "Unable read the required file 'xp.xml'.  Please download a new copy of this file.  If you have edited it manually, please restore an original copy.");
        }
      }
      else
      {
        Utility.ShowWarningMessage("Unable to locate required file 'xp.xml'.  Please make sure this file exists with the application exe file.  The application will not run properly without it.", "File Not Found");
      }
    }

    /// <summary>
    /// Retursn the player day if found.  otherwise null;
    /// </summary>
    /// <param name="SeasonDay"></param>
    /// <returns></returns>
    public PlayerDay GetPlayerDay(string SeasonDay)
    {
      if (PlayerDays.ContainsKey(SeasonDay))
      {
        return PlayerDays[SeasonDay];
      }
      else
      {
        return null;
      }
    }


    /// <summary>
    /// Trains the passed attribute at (approx normal pct) * (passed percentageincrease)
    /// </summary>
    /// <param name="Attr"></param>
    /// <param name="PercentageIncrease"></param>
    /// <returns></returns>
    public Boolean TrainAttribute(string SeasonDay, string Attr, double PercentageIncrease)
    {
      PlayerDay pd = GetPlayerDay(SeasonDay);
      if (pd.RunningBuildTotals.ContainsKey(Attr))
      {
        int RoundedSkillAmount = (int)Math.Round(pd.RunningBuildTotals[Attr].CurrentAmount);
        int TrainPercentBySkillAmount = 5;
        if (m_TrainingPercents.ContainsKey(RoundedSkillAmount))
        {
          TrainPercentBySkillAmount = m_TrainingPercents[RoundedSkillAmount];
        }
        double TrainingPctToAdd = TrainPercentBySkillAmount * (PercentageIncrease / 100);
        pd.RunningBuildTotals[Attr].TrainingBarPct += (int)Math.Round(TrainingPctToAdd, 0);
        return true;
      }
      return false;
    }

    /// <summary>
    /// returns the approx train pct for the passed attribute
    /// </summary>
    /// <param name="Attr"></param>
    /// <returns></returns>
    public int ApproxTrainPercentage(String SeasonDay, String Attr)
    {
      PlayerDay pd = GetPlayerDay(SeasonDay);
      if (pd.RunningBuildTotals.ContainsKey(Attr))
      {
        int RoundedSkillAmount = (int)Math.Round(pd.RunningBuildTotals[Attr].CurrentAmount);
        int TrainPercentBySkillAmount = 5;
        if (m_TrainingPercents.ContainsKey(RoundedSkillAmount))
        {
          TrainPercentBySkillAmount = m_TrainingPercents[RoundedSkillAmount];
        }
        return TrainPercentBySkillAmount;
      }
      return 0;
    }

    /// <summary>
    /// SPends the passed SPs on the passed attribute.  Returns any unused SPs
    /// </summary>
    /// <param name="Attr"></param>
    /// <param name="SkillPoints"></param>
    /// <returns></returns>
    public int AddSkillPoints(string SeasonDay, string Attr, int SkillPoints)
    {
      int SpentSPs = 0;
      PlayerDay pd = GetPlayerDay(SeasonDay);
      if (pd.RunningBuildTotals.ContainsKey(Attr))
      {

        int SPsToMoveSkillByOne = GetSoftcapSkillPointsByAttrAmount(pd.RunningBuildTotals[Attr].CurrentAmount);
        //spend one SP, and recalculate until SPs are gone.
        while (SpentSPs <= SkillPoints - SPsToMoveSkillByOne)
        {
          //move skill by one and recalc softcap
          pd.RunningBuildTotals[Attr].CurrentAmount++;
          SpentSPs += SPsToMoveSkillByOne;
          SPsToMoveSkillByOne = GetSoftcapSkillPointsByAttrAmount(pd.RunningBuildTotals[Attr].CurrentAmount);
        }
      }
      else if (Attr.ToLower() == "special abilities")
      {
        SpentSPs = SkillPoints;
      }

      return SkillPoints - SpentSPs;
    }

    public Dictionary<int, int> GetApproxTrainingPcts()
    {
      return m_TrainingPercents;
    }

    /// <summary>
    /// Returns the number of skill points it takes to move an attribute by 1 point at the CurrentSkillAmount.
    /// </summary>
    /// <param name="CurrentSkillAmount"></param>
    /// <returns></returns>
    public int GetSoftcapSkillPointsByAttrAmount(double CurrentSkillAmount)
    {
      foreach (KeyValuePair<int, SoftcapRange> kvp in Softcaps)
      {
        if (kvp.Value.InRange(CurrentSkillAmount))
        {
          return kvp.Key;
        }
      }
      return 1;
    }


    /// <summary>
    /// Returns the total number of SPs in the build as of the passed season day, converted for softcaps.
    /// </summary>
    /// <returns></returns>
    public double TotalSkillPointsInBuild(string SeasonDay)
    {
      double TotalSPs = 0;

      PlayerDay pd = GetPlayerDay(SeasonDay);
      foreach (KeyValuePair<string, GLBAttribute> kvp in pd.RunningBuildTotals)
      {
        foreach (KeyValuePair<int, SoftcapRange> kvp2 in Softcaps)
        {
          TotalSPs += kvp2.Value.TotalSPsInThisRange(kvp.Value.CurrentAmount);
        }
      }
      return Math.Round(TotalSPs, 2);
    }


    /// <summary>
    /// Adds the passed amount to the training points and updates the labels
    /// </summary>
    /// <param name="Amount"></param>
    public void SpendTrainingPoints(int Amount)
    {
      UnspentTP += Amount;
    }

    /// <summary>
    /// Adds the passed amount to the Skill Points and updates the labels
    /// </summary>
    /// <param name="Amount"></param>
    public void SpendSkillPoints(int Amount)
    {
      UnspentSP += Amount;
    }

    /// <summary>
    /// Adds the passed amount to the bonus tokens and updates the labels.
    /// </summary>
    /// <param name="Amount"></param>
    public void SpendBonusTokens(int Amount)
    {
      UnspentBT += Amount;
    }


    /// <summary>
    /// Runs nightly processing for the build.  Increments the Day and adds the daily XP.  Rolls level bonus if necessary
    /// </summary>
    /// <returns></returns>
    public string NightlyProcessing()
    {
      //Roll new seasonday
      IncrementDay();
      AddNightlyXP();
      return Utility.GetSeasonDayString(Season, Day);
    }

    public void RestartCurrentDay(Boolean GoBackOneDay)
    {

      //We need to roll back to prior day's finishing values.
      //I.e. Remove any boosts, reset all TP, SP, BT to values before use.
      //First get the prior Day.
      string CurrentDay = Utility.GetSeasonDayString(Season, Day);
      PlayerDay CurrentPlayerDay = GetPlayerDay(CurrentDay);


      //Unspend SPs, booosts, TPs.
      UnspentBoosts += CurrentPlayerDay.BoostsUsed;
      //SUBTRACT spS from Boosts
      Level -= CurrentPlayerDay.BoostsUsed;
      CurrentPlayerDay.BoostsUsed = 0;
      SpendTrainingPoints(CurrentPlayerDay.TPSpent);
      CurrentPlayerDay.TPSpent = 0;
      SpendSkillPoints(-CurrentPlayerDay.SPEarned);
      SpendSkillPoints(CurrentPlayerDay.SPSpent);
      CurrentPlayerDay.SPSpent = 0;
      //convert back any spent BTs from SPs
      //SpendSkillPoints(-(int)(CurrentPlayerDay.BTSpent / 4));
      SpendBonusTokens(CurrentPlayerDay.BTSpent);
      SpendBonusTokens(-CurrentPlayerDay.BTEarned);
      CurrentPlayerDay.BTSpent = 0;

      //Start by backing off the XP for the current day from the XP and Level vars.
      //Then remove the TPs Earned and Boosts Earned from the running totals
      //Then, delete the player day, backup the day and season to the prior 
      //and run Nightly Processing again.
      XP -= CurrentPlayerDay.DailyXP;
      XP -= CurrentPlayerDay.GameXP;
      if (XP < 0)
      {
        Level--;
        XP = 1000 + XP;
      }
      //Now back off any VetXP
      VetXP -= CurrentPlayerDay.DailyVetXP;
      if (VetXP < 0)
      {
        //if the VetXP goes less than zero, subtract the amount below zero it went from 1000 and that's the new VetXP
        VetXP = 1000 - Math.Abs(VetXP);
      }
      UnspentVetSP -= CurrentPlayerDay.VetSPEarned;

      UnspentTP -= CurrentPlayerDay.TPEarned;
      UnspentBoosts -= CurrentPlayerDay.BoostsEarned;
      PlayerDays.Remove(CurrentDay);
      Day--;
      if (Day < 0)
      {
        Day = 41;
        Season--;
      }
      if (!GoBackOneDay)
      {
        if (PlayerDays.Count == 1)
        {
          //we're at the beginning.  Restart with no trainingXP
          IncrementDay();
        }
        else
        {
          IncrementDay();
          AddNightlyXP();
        }
      }
    }

    /// <summary>
    /// increments the day.  If a season rollover occurs, it will reset day to 0, and add the season rollover TP
    /// returns the TPs earned
    /// </summary>
    private void IncrementDay()
    {
      PlayerDay PreviousPlayerDay = GetPlayerDay(Utility.GetSeasonDayString(Season, Day));
      if (PreviousPlayerDay == null)
      {
        //then use the 00-DD day, should be the only one in the dictionary
        foreach (KeyValuePair<string, PlayerDay> kvp in PlayerDays)
        {
          PreviousPlayerDay = kvp.Value;
          break;
        }
        Day = PreviousPlayerDay.Day;
        Season = 1;
        //now add the new day record to playerday
        PlayerDay pd = new PlayerDay(0, 0, Day, Season, 0, 0, 0, 0, PreviousPlayerDay.GetCopyOfRunningBuildTotals());
        PlayerDays.Add(Utility.GetSeasonDayString(Season, Day), pd);
      }
      else
      {
        Day++;
        int TPEarned = 2;
        int BoostsEarned = 0;
        if (Day > 41)
        {
          Season++;
          Day = 0;
          TPEarned = m_PreSeasonTP;
          BoostsEarned = 3;
        }
        else if (Day == 41)
        {
           TPEarned = m_OffSeasonTP;
        }
        UnspentTP += TPEarned;
        UnspentBoosts += BoostsEarned;
        //now add the new day record to playerday
        PlayerDay pd = new PlayerDay(0, 0, Day, Season, TPEarned, 0, 0, BoostsEarned, PreviousPlayerDay.GetCopyOfRunningBuildTotals());
        PlayerDays.Add(Utility.GetSeasonDayString(Season, Day), pd);
      }
    }

    /// <summary>
    /// adds the nightly processing XP and if a level up occurs processes the level bonus.
    /// </summary>
    /// <param name="pd"></param>
    private void AddNightlyXP()
    {
      if (Day > 0 && Day < 41)
      {
        int XPToAdd = 25;
        PlayerDay pd = GetPlayerDay(Utility.GetSeasonDayString(Season, Day));
        if (m_XPInfo.ContainsKey(Level))
        {
          XPToAdd = m_XPInfo[Level].DailyXP;
        }
        pd.DailyXP = XPToAdd;
        XP += XPToAdd;
        if (XP >= 1000)
        {
          XP = XP - 1000;
          LevelUp(pd);
        }

        #region VetXP
        //if over level 24, add VXP
        int VXPToAdd = 0;
        if (Level <= 24)
        {
          VXPToAdd = 0;
        }
        else if (Level < 30)
        {
          VXPToAdd = 125;
        }
        else if (Level < 35)
        {
          VXPToAdd = 150;
        }
        else if (Level < 40)
        {
          VXPToAdd = 175;
        }
        else
        {
          VXPToAdd = 200;
        }
        //now check for VSP
        VetXP += VXPToAdd;
        pd.DailyVetXP = VXPToAdd;
        if (VetXP - 1000 >= 0)
        {
          VetXP = VetXP - 1000;
          UnspentVetSP++;
        }
        #endregion
      }
    }

    /// <summary>
    /// adds the game XP and if a level up occurs processes the level bonus.
    /// Only processes the game if none has already been processed, AND there is a game today.
    /// </summary>
    /// <param name="pd"></param>
    public void ProcessGame()
    {
      PlayerDay pd = GetPlayerDay(Utility.GetSeasonDayString(Season, Day));
      if (pd.GameXP == 0 && Utility.GameToday(Day, GameDays))
      {
        double GameMultiplier = (double)m_MaxGameXPPct / 100;
        int XPToAdd = (int)(Math.Round(100 * (GameMultiplier), 0));
        if (m_XPInfo.ContainsKey(Level))
        {
          XPToAdd = (int)(Math.Round(m_XPInfo[Level].GameMaxXP * (GameMultiplier), 0));
        }
        pd.GameXP = XPToAdd;
        XP += XPToAdd;
        if (XP > 1000)
        {
          XP = XP - 1000;
          LevelUp(pd);
        }
      }
    }

    /// <summary>
    /// Boosts the player 1 level, if enough boosts are available
    /// </summary>
    /// <returns></returns>
    public Boolean Boost()
    {
      if (UnspentBoosts > 0)
      {
        PlayerDay pd = GetPlayerDay(Utility.GetSeasonDayString(Season, Day));
        UnspentBoosts--;
        pd.BoostsUsed++;
        LevelUp(pd);
        return true;
      }
      return false;
    }


    /// <summary>
    /// performs level up stuff (incrememtn level, process level bonus, add sp
    /// </summary>
    private void LevelUp(PlayerDay pd)
    {
      Level++;
      pd.ProcessLevelBonus(Level);
      if (Level == 25)
      {
        pd.VetSPEarned = 2;
        UnspentVetSP = 2;
      }
      UnspentSP += 5;
      pd.SPEarned += 5;
    }




  }

  /// <summary>
  /// Represents an attribute and it's training percentage
  /// </summary>
  [Serializable]
  public class GLBAttribute
  {
    private int m_TrainingBarPct;

    public enum AttributeType { atNone, atPrimary, atSecondary };
    public string AttributeName;
    public double CurrentAmount;
    public AttributeType attributeType = AttributeType.atNone;

    public GLBAttribute(string name, double Amount, int CurrentTrainingBarPct, AttributeType atType)
    {
      attributeType = atType;
      AttributeName = name;
      CurrentAmount = Amount;
      m_TrainingBarPct = CurrentTrainingBarPct;
    }

    ///// <summary>
    ///// Use this constructor to instantiate a Special Ability tree item
    ///// </summary>
    ///// <param name="name"></param>
    ///// <param name="Amount"></param>
    ///// <param name="CurrentTrainingBarPct"></param>
    ///// <param name="atType"></param>
    //public GLBAttribute(string name, double Amount, int CurrentTrainingBarPct, AttributeType atType)
    //{
    //  attributeType = atType;
    //  AttributeName = name;
    //  CurrentAmount = Amount;
    //  m_TrainingBarPct = CurrentTrainingBarPct;
    //}


    public string GetAttributeAmountForDisplay()
    {
      return Math.Round(CurrentAmount, 2).ToString();
    }

    public int TrainingBarPct
    {
      get { return m_TrainingBarPct; }
      set
      {
        m_TrainingBarPct = value;
        if (m_TrainingBarPct >= 100)
        {
          CurrentAmount++;
          m_TrainingBarPct = m_TrainingBarPct - 100;
        }
      }
    }
  }  
  
  ///// <summary>
  ///// Represents an attribute and it's training percentage
  ///// </summary>
  //[Serializable]
  //public class GLBSATreeItem
  //{

  //  public enum SAType { satSingle, satDouble };
  //  public string SAName;
  //  public int CurrentAmount;
  //  public SAType SAType = AttributeType.satSingle;

  //  /// <summary>
  //  /// Use this constructor to instantiate a Special Ability tree item
  //  /// </summary>
  //  /// <param name="name"></param>
  //  /// <param name="Amount"></param>
  //  /// <param name="CurrentTrainingBarPct"></param>
  //  /// <param name="atType"></param>
  //  public GLBSATreeItem(string name, int Amount, SAType satType)
  //  {
  //    SAType = satType;
  //    AttributeName = name;
  //    CurrentAmount = Amount;;
  //  }


  //  public string GetAmountForDisplay()
  //  {
  //    return Math.Round(CurrentAmount, 2).ToString();
  //  }

  //}


  [Serializable]
  public class PlayerData
  {
    public int Season;
    public int Day;
    public int Level;
    public int VSP;
    public int VXP;


    /// <summary>
    /// Level XP total out of 1000
    /// </summary>
    public int LevelXP
    {
      get
      {
        return m_LevelXP;
      }
      set
      {
        if (value - 1000 >= 0)
        {
          m_LevelXP = value - 1000;
          Level++;
        }
        else
        {
          m_LevelXP = value;
        }
      }
    }

    private int m_LevelXP;

    public PlayerData(int _Season, int _Day, int _LevelXP, int _Level, int _VXP)
    {
      Season = _Season;
      Day = _Day;
      Level = _Level;
      m_LevelXP = _LevelXP;
      VXP = _VXP;
    }

    public int DailyVXPEarned()
    {
      if (Level < 25)
      {
        return 0;
      }
      if (Level < 30)
      {
        return 125;
      }
      if (Level < 35)
      {
        return 150;
      }
      if (Level < 40)
      {
        return 175;
      }
      return 200;
    }
  }




  /// <summary>
  /// Stats for a player's day.
  /// This is how the player looks immediately before nightly rollover.
  /// Corresponds directly to the DailyLook grid.
  /// </summary>
  [Serializable]
  public class PlayerDay
  {
    public int Season;
    public int Day;
    public int GameXP;
    public int DailyXP;
    public int DailyVetXP;
    public int VetSPEarned;
    public int VetSPSpent = 0;
    public int TPEarned;
    public int SPEarned;
    public int BTEarned;
    public int BoostsEarned;
    public int SPSpent = 0;
    public int TPSpent = 0;
    public int BTSpent = 0;
    public int BoostsUsed = 0;
    public Dictionary<string, GLBAttribute> RunningBuildTotals;

    private int m_NumPrimaries = 0;
    private int m_NumSecondaries = 0;

    public PlayerDay(int _GameXP, int _DailyXP, int _Day, int _Season, int _TPEarned, int _SPEarned, int _VetSPEarned,
                     int _BoostsEarned, Dictionary<string, GLBAttribute> StartingAttributes)
    {
      VetSPEarned = _VetSPEarned;
      Day = _Day;
      GameXP = _GameXP;
      DailyXP = _DailyXP;
      Season = _Season;
      TPEarned = _TPEarned;
      SPEarned = _SPEarned;
      BoostsEarned = _BoostsEarned;
      RunningBuildTotals = StartingAttributes;

      #region SetPrimaries Counts
      foreach (KeyValuePair<string, GLBAttribute> kvp in RunningBuildTotals)
      {
        switch (kvp.Value.attributeType)
        {
          case GLBAttribute.AttributeType.atPrimary:
            m_NumPrimaries++;
            break;
          case GLBAttribute.AttributeType.atSecondary:
            m_NumSecondaries++;
            break;
        }
      }
      #endregion

      if (m_NumPrimaries < 1)
        m_NumPrimaries = 1;
      if (m_NumSecondaries < 1)
        m_NumSecondaries = 1;
    }


    /// <summary>
    /// Increments all skills based on the level bonus.
    /// </summary>
    /// <param name="PlayerLevel"></param>
    public void ProcessLevelBonus(int NextPlayerLevel)
    {
      const double PRIMARIES_BONUS = 2.0;
      const double SECONDARIES_BONUS = 1.0;
      const double LEVEL_BONUS_MULTIPLIER = 0.75;


      int PlayerModifierLevel = NextPlayerLevel - 22;
      if (PlayerModifierLevel < 0)
      {
        PlayerModifierLevel = 0;
      }

      //Hack to top diminishing returns out at level 38.  
      //This is a hack b/c I don't have proof that this is true yet.
      if (PlayerModifierLevel > 24) PlayerModifierLevel = 24;

      double Power = Math.Ceiling((double)PlayerModifierLevel / 8);
      double Multiplier = Math.Pow(LEVEL_BONUS_MULTIPLIER, Power);

      foreach (KeyValuePair<string, GLBAttribute> kvp in RunningBuildTotals)
      {
        switch (kvp.Value.attributeType)
        {
          case GLBAttribute.AttributeType.atPrimary:
            kvp.Value.CurrentAmount += Math.Round(PRIMARIES_BONUS * Multiplier / m_NumPrimaries, 2);
            break;
          case GLBAttribute.AttributeType.atSecondary:
            kvp.Value.CurrentAmount += Math.Round(SECONDARIES_BONUS * Multiplier / m_NumSecondaries, 2);
            break;
        }
      }
    }

    /// <summary>
    /// Returns a copy of this object's runningbuildtotals dictionary
    /// </summary>
    /// <returns></returns>
    public Dictionary<string, GLBAttribute> GetCopyOfRunningBuildTotals()
    {
      Dictionary<string, GLBAttribute> result = new Dictionary<string, GLBAttribute>();
      foreach (KeyValuePair<string, GLBAttribute> kvp in RunningBuildTotals)
      {
        GLBAttribute attr = new GLBAttribute(kvp.Key, kvp.Value.CurrentAmount, kvp.Value.TrainingBarPct, kvp.Value.attributeType);
        result.Add(kvp.Key, attr);
      }
      return result;
    }
  }



  public static class Utility
  {
    public static void ShowErrorMesssage(Exception ex, string AdditionalMessage)
    {
      MessageBox.Show(AdditionalMessage + "Error Message: " + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

    public static void ShowWarningMessage(string Message, string Caption)
    {
      MessageBox.Show(Message, Caption, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
    }

    public static void ShowInfoMessage(string Message, string Caption)
    {
      MessageBox.Show(Message, Caption, MessageBoxButtons.OK, MessageBoxIcon.Information);
    }

    /// <summary>
    /// Returns the level of the player for the passed XP amount
    /// </summary>
    /// <param name="CurrentXP"></param>
    /// <returns></returns>
    public static int GetLevel(int CurrentXP)
    {
      double XP = CurrentXP / 1000;
      return (int)(Math.Truncate(XP) + 1);
    }

    /// <summary>
    /// Calculates if there is a game today based on the current day and the Game Day string.
    /// </summary>
    /// <param name="Day"></param>
    /// <param name="GameDay">Odd Days, Even Days or none</param>
    /// <returns></returns>
    public static Boolean GameToday(int Day, string GameDay)
    {
      return (
              (
               (GameDay == "Odd Days" && (Day % 2 == 1)) ||
               (GameDay == "Even Days" && (Day % 2 == 0))
              ) &&
              Day <= 32 && Day > 0
             );
    }

    /// <summary>
    /// returns the season day in (season) - (day) format with leading 0s for proper sorting.
    /// </summary>
    /// <param name="Season"></param>
    /// <param name="Day"></param>
    /// <returns></returns>
    public static string GetSeasonDayString(int Season, int Day)
    {
      string strSeason = Season.ToString();
      string strDay = Day.ToString();
      while (strDay.Length < 2)
      {
        strDay = "0" + strDay;
      }
      while (strSeason.Length < 2)
      {
        strSeason = "0" + strSeason;
      }
      return String.Format("{0} - {1}", strSeason, strDay);
    }





    public static DialogResult QuestionBox(string Question, MessageBoxButtons messageBoxButtons)
    {
      return MessageBox.Show(Question, "Question", messageBoxButtons, MessageBoxIcon.Exclamation);
    }

    public static void SaveErrorInformation(Exception ex, String Message)
    {
      Guid guid = Guid.NewGuid();
      string Filename = guid.ToString() + ".txt";
      // create a writer and open the file
      using (TextWriter tw = new StreamWriter(Filename))
      {
        // write a line of text to the file
        tw.WriteLine("Exception: ");
        tw.WriteLine(ex.Message);
        tw.WriteLine("Stack: ");
        tw.WriteLine(ex.StackTrace);
        tw.WriteLine("Source: ");
        tw.WriteLine(ex.Source);
        tw.WriteLine("Message: ");
        tw.WriteLine(Message);
        // close the stream
        tw.Close();
      }

    }
  }
}
